LIREで画像の類似検索をしてみる
LIREという画像検索のためのライブラリを試してみました。 本記事では以下の内容について記載しています。
- LIREとは
- サンプルを動かしてみる
- インデックス作成と検索のコードを書いてみる
LIREとは
LIRE (Lucene Image Retrieval) is an open source library for content based image retrieval, which means you can search for images that look similar.
LIREは、似ている画像を検索するためのOSSのライブラリです。Javaです。
画像の特徴に対するLuceneのインデックスを作成し、作成したインデックスに対して画像での検索が行えます。
ドキュメントはこちらからご確認ください。
サンプルを動かしてみる
README.md
いわくアプリケーションに組み込みたい場合はサンプルアプリケーションを見てみると良い、とのことなのでざっくり動かしてみます。
サンプルでは、「ディレクトリ配下の画像ファイルのインデクシング」と「指定した画像に似ている画像のランク付け」ができます。
ダウンロード
こちらのSample Applicationからプロジェクトをダウンロードします。
解凍してコンソールからプロジェクトのルートディレクトリに移動します。
サンプルに利用する画像のディレクトリ設定
runIndexingのargsに画像ファイルが含まれるディレクトリのパスを、runSearchのargsに検索対象の画像ファイルのパスを指定します
task runIndexing(type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'net.semanticmetadata.lire.sampleapp.ParallelIndexing' // Define the directory where to find the images to index. args '/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg' } task runSearch(type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'net.semanticmetadata.lire.sampleapp.Searcher' // Define the image to be used as query args '/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg' }
動かしてみる
インデクシング
指定した画像ファイルをインデクシングします。
$ ./gradlew runIndexing :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :runIndexing Indexing images in /Users/inaba.jun/Desktop/ss/test Getting all images in /Users/inaba.jun/Desktop/ss/test including those in subdirectories ~ Found 10 images =================================================================================== SetUp: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Set of GlobalFeatures: AutoColorCorrelogram CEDD FCTH =================================================================================== No need for sampling and generating codebooks..... Indexing 10 images Analyzed 10 images in 00:09 ~ 967.70 ms each. Total time of indexing: 00:09. Properties saved! Finished indexing. BUILD SUCCESSFUL Total time: 31.392 secs
作成されたインデックスはindexディレクトリ配下に格納されます。
$ ls -la index total 104 drwxr-xr-x 11 inabajunmr staff 352 1 30 18:27 . drwx------@ 15 inabajunmr staff 480 1 30 19:45 .. -rw-r--r-- 1 inabajunmr staff 21063 1 30 18:27 _2.fdt -rw-r--r-- 1 inabajunmr staff 93 1 30 18:27 _2.fdx -rw-r--r-- 1 inabajunmr staff 218 1 30 18:27 _2.fnm -rw-r--r-- 1 inabajunmr staff 419 1 30 18:27 _2.si -rw-r--r-- 1 inabajunmr staff 110 1 30 18:27 _2_Lucene50_0.doc -rw-r--r-- 1 inabajunmr staff 1809 1 30 18:27 _2_Lucene50_0.tim -rw-r--r-- 1 inabajunmr staff 243 1 30 18:27 _2_Lucene50_0.tip -rw-r--r-- 1 inabajunmr staff 143 1 30 18:27 segments_2 -rw-r--r-- 1 inabajunmr staff 0 1 30 18:26 write.lock
検索
画像を検索します。 検索対象の画像には以下を使います。
$ ./gradlew runSearch :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :runSearch 0.0: /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_young_woman.jpg 3.7023253150876627: /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_young_man.jpg 14.759058277769086: /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_woman.jpg 15.771364119843213: /Users/inaba.jun/Desktop/ss/test/kaisya_desk1_syachou_man.jpg 46.36033535484118: /Users/inaba.jun/Desktop/ss/test/banzai_school.jpg 53.132664072463285: /Users/inaba.jun/Desktop/ss/test/business_sabori_pc_solitaire.jpg 57.03463611351899: /Users/inaba.jun/Desktop/ss/test/surprise_party_woman.jpg 58.86276653909239: /Users/inaba.jun/Desktop/ss/test/surprise_party_man.jpg 59.136212624584715: /Users/inaba.jun/Desktop/ss/test/moratorium_man.jpg 62.14486553621638: /Users/inaba.jun/Desktop/ss/test/moratorium_woman.jpg BUILD SUCCESSFUL Total time: 3.955 secs
サンプルアプリケーションでは、似ている順に30位までのスコアと画像ファイルのパスが出力されます。 ちなみにスコアが小さい方がより似ている画像です。
検索対象の画像は以下です。
検索結果をスコア順に並べると以下のようになります。だいぶそれっぽい結果が出ました。
サンプルアプリケーションのソースコードは以下です。
サンプルと同じようなアプリを自分で実装してみる
実際にライブラリをどう使うのか確認するために画像のインデックス作成と検索機能を実装してみます。
Lireのビルド
Maven Centralに対象のライブラリがなかったので今回はローカルでビルドしたものを使います。
こちらのページからLireをダウンロードします。
ダウンロードしたファイル解凍し当該のディレクトリに移動してから、以下のコマンドでビルドします。
$ cd /Users/inaba.jun/Downloads/Lire-1.0b4 $ ./gradlew build
build/libs
にビルドされたjarファイルができます。
コード
Gradleプロジェクトを新規作成します。 libディレクトリ配下に先ほどのjarファイルを配置します。
build.gradlew
version '1.0-SNAPSHOT' apply plugin: 'java' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'org.apache.lucene', name: 'lucene-core', version: '6.3.0' compile group: 'org.apache.lucene', name: 'lucene-analyzers-common', version: '6.3.0' compile group: 'org.apache.lucene', name: 'lucene-queryparser', version: '6.3.0' compile group: 'commons-io', name: 'commons-io', version: '2.5' compile group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1' compile group: 'com.sangupta', name: 'jopensurf', version: '1.0.0' compileOnly 'org.projectlombok:lombok:1.16.10' compile fileTree(dir: 'lib', include: '*.jar') } task index(type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'IndexMain' // 第一引数に画像ファイルを配置したディレクトリ、第二引数にインデックスの生成先 args('/Users/inaba.jun/git/lire-sample/src/main/resources/imgs','/Users/inaba.jun/git/lire-sample/src/main/resources/index') } task search(type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'SearchMain' // 第一引数に検索対象の画像ファイル、第二引数にインデックスの生成先 args('/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg','/Users/inaba.jun/git/lire-sample/src/main/resources/index') }
Java
インデクシング
<br />import net.semanticmetadata.lire.builders.GlobalDocumentBuilder; import net.semanticmetadata.lire.imageanalysis.features.global.AutoColorCorrelogram; import net.semanticmetadata.lire.imageanalysis.features.global.CEDD; import net.semanticmetadata.lire.imageanalysis.features.global.FCTH; import org.apache.lucene.analysis.core.WhitespaceAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.FSDirectory; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class IndexMain { public static void main(String[] args) { String imageDir = args[0]; String indexPath = args[1]; indexDir(Paths.get(imageDir), Paths.get(indexPath)); } private static void indexDir(Path imageDirPath, Path indexPath) { try { // ディレクトリの全てのファイルからインデックス作成 Files.list(imageDirPath).forEach(f -> indexFile(f, indexPath)); } catch (IOException e) { e.printStackTrace(); } } private static void indexFile(Path imageFilePath, Path indexPath) { IndexWriterConfig conf = new IndexWriterConfig(new WhitespaceAnalyzer()); try (IndexWriter indexWriter = new IndexWriter(FSDirectory.open(indexPath), conf);) { GlobalDocumentBuilder globalDocumentBuilder = new GlobalDocumentBuilder(false, false); globalDocumentBuilder.addExtractor(CEDD.class); globalDocumentBuilder.addExtractor(FCTH.class); globalDocumentBuilder.addExtractor(AutoColorCorrelogram.class); BufferedImage img = ImageIO.read(new FileInputStream(imageFilePath.toString())); // 指定したディレクトリに画像のインデックスを作成 Document document = globalDocumentBuilder.createDocument(img, imageFilePath.toString()); indexWriter.addDocument(document); } catch (IOException e) { e.printStackTrace(); } } }
検索
import net.semanticmetadata.lire.builders.DocumentBuilder; import net.semanticmetadata.lire.imageanalysis.features.global.CEDD; import net.semanticmetadata.lire.searchers.GenericFastImageSearcher; import net.semanticmetadata.lire.searchers.ImageSearchHits; import net.semanticmetadata.lire.searchers.ImageSearcher; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexableField; import org.apache.lucene.store.FSDirectory; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; import java.util.Set; public class SearchMain { public static void main(String[] args) { String imageFile = args[0]; String indexPath = args[1]; // 検索対象の画像とインデックスのパスを指定して検索 search(Paths.get(imageFile), Paths.get(indexPath)); } private static void search(Path imagePath, Path indexPath) { BufferedImage img = null; try { img = ImageIO.read(imagePath.toFile()); IndexReader indexReader = DirectoryReader.open(FSDirectory.open(indexPath)); // 最大10件まで検索 ImageSearcher searcher = new GenericFastImageSearcher(10, CEDD.class); ImageSearchHits hits = searcher.search(img, indexReader); for (int i = 0; i < hits.length(); i++) { Set<String> fields = new HashSet<>(); fields.add(DocumentBuilder.FIELD_NAME_IDENTIFIER); String identifier = indexReader .document(hits.documentID(i), fields) .getField(DocumentBuilder.FIELD_NAME_IDENTIFIER).stringValue(); // マッチした画像のスコアとパスを出力 System.out.println("Score:" + hits.score(i) + "\tFile:" + identifier); } } catch (IOException e) { e.printStackTrace(); } } }
実行してみる
インデクシング
$ ./gradlew index :compileJava UP-TO-DATE :processResources :classes :index BUILD SUCCESSFUL Total time: 4.251 secs
検索
$ ./gradlew search :compileJava :processResources UP-TO-DATE :classes :search Score:0.0 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_woman.jpg Score:3.7023253150876627 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_young_man.jpg Score:14.759058277769086 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_woman.jpg Score:15.771364119843213 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/kaisya_desk1_syachou_man.jpg Score:46.36033535484118 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/banzai_school.jpg Score:53.132664072463285 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/business_sabori_pc_solitaire.jpg Score:59.136212624584715 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/moratorium_man.jpg Score:62.14486553621638 File:/Users/inaba.jun/git/lire-sample/src/main/resources/imgs/moratorium_woman.jpg BUILD SUCCESSFUL Total time: 6.081 secs
やっていることは同じなのでサンプルと同様の結果が出ました。
まとめ
- LIREは、似ている画像を検索するためのJavaのライブラリ
- 画像の特徴に対するLuceneのインデックスを作成したり、インデックスに対して検索したりできる
本記事では、LIREを使って「画像のインデクシング」と「画像の検索」をしてみました。
私からは以上です。